/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds.object;

import java.util.ArrayList;
import net.worlds.WorldsException;
import net.worlds.WorldsSettings;
import net.worlds.Main;
import net.worlds.PropChangeHandler;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.shapes.PolygonDef;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.Settings;
import org.jbox2d.common.Vec2D;
import org.jbox2d.dynamics.Body;


public class Water extends Box implements Steppable{
    private ArrayList<Body> bodies = new ArrayList<Body>(512);
    private final BuoyancyController controller = new BuoyancyController();
    Vec2D normal = new Vec2D(0, 1);
    float offset = 0;
    float linearDrag = 2;
    float angularDrag = 1;
    float density = 2f;
    Vec2D velocity = new Vec2D(0, 0);
    public Water() throws WorldsException
    {
        super("Water");
        addPropChangeHandler("Size", new PropChangeHandler()
        {
            @Override
            public void handle(final Object oldValue, final Object value) throws WorldsException {

                try {
                    Vec2D vec = (Vec2D) value;
                    ((PolygonDef) sd).setAsBox((float) vec.x / 2, (float) vec.y / 2);
                    if(inWorld)
                    {
                        rebuildShape();
                        getBody().resetFiltering();
                    }
                    size = (Vec2D) value;
                    calculateOffset(vec);
                } catch (WorldsException ex) {
                    Main.error(ex);
                }
            }
        });
        addPropChangeHandler("Position", new PropChangeHandler()
        {
            @Override
            public void handle(final Object oldValue, final Object value) throws WorldsException {
                if(inWorld)
                {
                    Vec2D pos = (Vec2D) value;
                    body.setXForm(pos, body.getAngle());
                    body.wakeUp();
                    wakeUpConnected();
                    calculateOffset(getVec2DProp("Size"));
                }
                else
                    bd.position = ((Vec2D)value);
            }
        });
        addPropChangeHandler("Drag", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                linearDrag = (Float) value;
            }
        });
        addPropChangeHandler("Current", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                velocity = ((Vec2D)value);
            }
        });
        addPropChangeHandler("WaterDensity", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                density = (Float) value;
            }
        });
    }
    private void calculateOffset(Vec2D size)
    {
        offset = (float) (getPosition().y + size.y / 2);
    }
    @Override
    public void step() throws WorldsException
    {
        if(disabled)
            return;
        for(Body b : bodies)
        {
            for(Part p : b.getParts())
            {
                if(p != null)
                    p.inWaterNow = false;
            }
            b.waterVelocity = null;
        }
        bodies.clear();
        AABB bounds = new AABB();
        Vec2D sizeVec = size;
        bounds.lowerBound.set((float) getPosition().x - sizeVec.x/2, (float) getPosition().y - sizeVec.y/2);
        bounds.upperBound.set((float) getPosition().x + sizeVec.x/2, (float) getPosition().y + sizeVec.y/2);
        Shape[] shapes = world.query(bounds, WorldsSettings.MAX_OBJECTS);
        for(Shape s : shapes)
        {
            if(s.getUserData() == null || s.getUserData().inWaterNow || s.getBody().isStatic())
                continue;
            s.getUserData().inWater = true;
            s.getUserData().inWaterNow = true;
            if(!bodies.contains(s.getBody()) && s.getBody().getParts().length > 0)
                bodies.add(s.getBody());
        }
        controller.run();
    }
    private class BuoyancyController
    {
        public void run() {
            for(Body body : bodies)
            {
                if(body.isStatic())
                    continue;
                body.wakeUp();
                Vec2D areac = new Vec2D(0, 0);
                Vec2D massc = new Vec2D(0, 0);
                float area = 0;
                float mass = 0;
                boolean skip = false;
                for(Shape shape = body.getShapeList(); shape != null; shape = shape.getNext())
                {
                    //System.err.println(shape.getUserData());
                    Vec2D sc = new Vec2D(0, 0);
                    float sarea = shape.computeSubmergedArea(normal, offset, body.getXForm(), sc);
                    sc = shape.m_c;
                    area += sarea;
                    areac.x += sarea * sc.x;
                    areac.y += sarea * sc.y;
                    float shapeDensity = shape.m_density;
                    if(shapeDensity == 0)
                        skip = true;
                    mass += sarea * shapeDensity;
                    massc.x += sarea * sc.x * shapeDensity;
                    massc.y += sarea * sc.y * shapeDensity;
                }
                areac.x /= area;
                areac.y /= area;
                massc.x /= mass;
                massc.y /= mass;
                if(area < Settings.EPSILON || skip)
                    continue;
                Vec2D buoyancyForce = world.getGravity().mul(-density * area);
                body.applyForce(buoyancyForce, massc);
                Vec2D dragForce = body.getLinearVelocityFromWorldPoint(areac).sub(velocity);
                dragForce = dragForce.mul(-linearDrag * area);
                body.applyForce(dragForce, areac);
                body.waterVelocity = velocity;
                body.applyTorque(-body.getInertia()/body.getMass()*area*body.getAngularVelocity()*angularDrag);
            }
        }
    }
    @Override
    public void destructor() throws WorldsException
    {
        super.destructor();
        for(Body b : bodies)
        {
            for(Part p : b.getParts())
            {
                if(p != null)
                    p.inWaterNow = false;
            }
        }
    }
}
